#pragma once

namespace IcyCrystal {
	string identation;
	void printArgList(ArgList* list, ostream& out);
	string globalTypeToString(GlobalDeclaration decl){
		GlobalType type = decl.globalType;
		switch(type){
		case INT:
			return "int";
		case FLOAT:
			return "float";
		case STRING:
			return "string";
		case ARRAY:
			return "array";
		case LABEL:
			return *decl.symbolName + ":";
		case SYMBOL:
			return "";
		case POINTER:
			return "pointer";
		default:
			return "GLOBAL_TYPE_UNKNOWN";
		}
	}

	string globalDeclarationToString(GlobalDeclaration& decl){
		stringstream s;
		s << globalTypeToString(decl) << " " << (decl.symbolName && decl.globalType != LABEL && decl.globalType != STRING? *decl.symbolName : "") << " ";
		vector<GlobalDeclaration>::iterator it;
		switch(decl.globalType){
		case INT:
			s << decl.value.intValue;
			break;
		case FLOAT:
			s << decl.value.floatValue;
			break;
		case ARRAY:
			s << endl;
			for( it = decl.value.arrayValue->begin(); it!=decl.value.arrayValue->end(); it++){
				GlobalDeclaration& iDec = *it;
				//iDec.symbolName = NULL;
				s << "\t\t" << globalDeclarationToString(iDec) << endl;
			}
			break;
		case STRING:
			s << "\"" << *decl.value.stringValue << "\"";
			break;
		case LABEL:
			s << *decl.symbolName;
			break;
		case POINTER:
			s << globalDeclarationToString(decl.value.arrayValue->at(0));
			break;
		}
		return s.str();
	}

	string argToString(Arg& arg){
		stringstream s;
		switch(arg.arg){
		case EBP:
			return "ebp";
		case ESP:
			return "esp";
		case EBP_OFFSET:
			s << arg.name.intName << "(ebp)";
			return s.str();
		case INT_CONSTANT:
			s << arg.name.intName;
			return s.str();
		case LOCAL:
		case GLOBAL:
		case TEMP:
		case CONSTANT:
			s << *arg.name.symbolName;
			return s.str();
		case REGISTER:
			s << "r" << arg.name.intName;
			return s.str();
		default:
			return "UNKNOWN_ARG";
		}
	}

	string opToString(OpType op){
		switch(op){
		case PUSH:
			return "push ";
		case POP:
			return "pop ";
		case MOV:
			return "mov ";
		case SUB:
			return "sub ";
		case ADD:
			return "add ";
		case RETURN:
			return "ret ";
		case MULT:
			return "mult ";
		case DIV:
			return "div ";
		}
	}

	string quadToString(Quad* quad){
		stringstream s;
		switch(quad->op){
		case GLOBAL_DECL:
			return globalDeclarationToString(quad->inner.declaration);
			break;
		case PUSH:
		case POP:
			s << opToString(quad->op) << argToString(quad->inner.normalOp.arg1);
			return s.str();
		case MOV:
		case SUB:
		case ADD:
		case MULT:
		case DIV:
			if( quad->inner.normalOp.result.arg != NONE ){
				s << argToString(quad->inner.normalOp.result) << " = ";
				if( quad->op != MOV ) {
					s << argToString(quad->inner.normalOp.arg1) << " " << opToString(quad->op)
			 			<< argToString(quad->inner.normalOp.arg2);
				}
				else {
					s << argToString(quad->inner.normalOp.arg1);
				}
			}
			else {
				s << opToString(quad->op) << argToString(quad->inner.normalOp.arg1) << ", " << argToString(quad->inner.normalOp.arg2);
			}
			
			return s.str();
		case RETURN:
			s << opToString(quad->op);
			return s.str();
		case BLOCK:
			identation += "\t";
			printArgList(quad->inner.blockChildren, s);
			identation = identation.substr(0, identation.length() -1 );
			return s.str();
		default:
			return "UNKNOWN_OP";
		}
	}

	void printArgList(ArgList* list, ostream& out){
		ArgList::iterator it;
		for(it = list->begin(); it!= list->end(); it++){
			Quad* item = *it;
			if( item->op == GLOBAL_DECL && item->inner.declaration.globalType == LABEL)
				identation = identation.substr(0, identation.length() -1 );
			out << identation << quadToString(item) << endl;
			if( item->op == GLOBAL_DECL && item->inner.declaration.globalType == LABEL)
				identation += "\t";
				
		}
	}
}
